﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace Krile.Forms.MainForm.Controls.XTab
{
    public partial class XTab : Control
    {
        XTabMouseManager mouseMngr;
        XTabMover mover;
        public XTab()
        {
            InitializeComponent();
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.Opaque, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            SetStyle(ControlStyles.ResizeRedraw, true);
            SetStyle(ControlStyles.Selectable, true);
            SetStyle(ControlStyles.UserPaint, true);
            tabItems = new List<XTabItem>();
            mouseMngr = new XTabMouseManager(this);
            mover = new XTabMover(this);
            InitTabButtons();
        }

        public event EventHandler OnSelectedTabChanged;
        private void TabChangedInvoke()
        {
            if (OnSelectedTabChanged != null)
                OnSelectedTabChanged.Invoke(this, new EventArgs());
        }

        //put into faraway
        private static readonly Point FARAWAY = new Point(int.MinValue,int.MinValue);

        private Size maxTabSize = new Size(200, 20);
        /// <summary>
        /// max tab size which you allow
        /// </summary>
        /// <remarks>
        /// if you use fixed size mode,all tab will set this size.
        /// </remarks>
        public Size MaxTabSize
        {
            get { return maxTabSize; }
            set
            {
                maxTabSize = value;
                Refresh();
            }
        }

        private bool fixedSize = false;
        /// <summary>
        /// using fixed size with tab
        /// </summary>
        public bool FixedSize
        {
            get { return fixedSize; }
            set
            {
                fixedSize = value;
                Refresh();
            }
        }

        private bool multiLineMode = false;
        /// <summary>
        /// using tab with multiline
        /// </summary>
        public bool MultiLineMode
        {
            get { return multiLineMode; }
            set
            {
                multiLineMode = value;
                scrollIdx = 0;
                Refresh();
            }
        }

        /// <summary>
        /// scroll index
        /// </summary>
        private int scrollIdx = 0;

        object reflocker = new object();
        volatile bool _reqrefresh;
        volatile bool _refreshing;

        public override void Refresh()
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new MethodInvoker(Refresh));
            }
            else
            {
                //for freeze prevention
                lock (reflocker)
                {
                    if (_refreshing)
                    {
                        _reqrefresh = true;
                        return;
                    }
                    _refreshing = true;
                }

                do
                {
                    _reqrefresh = false;
                    base.Refresh();
                    Application.DoEvents();
                } while (_reqrefresh);

                _refreshing = false;
            }
        }

        private static object tabItemLocker = new object();
        private static object tabButtonLocker = new object();

        private List<XTabItem> tabItems;
        private Dictionary<string, XTabButtonItem> tabButtons;

        /// <summary>
        /// Move current tab
        /// </summary>
        /// <param name="newIndex">new index</param>
        public void SetCurrentTabIndex(int newIndex)
        {
            tabItems.Remove(SelectedItem);
            if (newIndex >= tabItems.Count)
                tabItems.Add(SelectedItem);
            else
                tabItems.Insert(newIndex, SelectedItem);
        }

        private volatile XTabItem _selectedItem = null;
        public XTabItem SelectedItem
        {
            get { return _selectedItem; }
            set
            {
                if (_selectedItem == value) return;
                if (_selectedItem != null)
                    _selectedItem.Selected = false;

                _selectedItem = value;
                if (_selectedItem != null)
                    _selectedItem.Selected = true;
                Refresh();
                TabChangedInvoke();
            }
        }

        /// <summary>
        /// enumerate all items for testing mouse motion
        /// </summary>
        /// <returns>itembase array</returns>
        public XTabItemBase[] GetItemBaseArray()
        {

            List<XTabItemBase> rets = new List<XTabItemBase>();

            // You "MUST" REGIST tabButtons BEFORE tabItems!!

            lock (tabButtonLocker)
            {
                if (tabButtons != null)
                {
                    foreach (XTabButtonItem item in tabButtons.Values)
                        rets.Add(item);
                }
            }
            lock (tabItemLocker)
            {
                if (tabItems != null)
                    rets.AddRange(tabItems.ToArray());
            }
            return rets.ToArray();
        }

        /// <summary>
        /// add new item tab
        /// </summary>
        /// <param name="item">tab item</param>
        public void AddTab(XTabItem item)
        {
            lock (tabItemLocker)
                tabItems.Add(item);
            SelectedItem = item;
            this.Refresh();
        }

        /// <summary>
        /// close tab
        /// </summary>
        /// <param name="item">tab item</param>
        public void CloseTab(XTabItem item)
        {
            lock (tabItemLocker)
            {
                if (tabItems.Contains(item))
                {
                    if (item.Selected)
                    {
                        //inherit selection
                        if (tabItems.Count == 1)
                            SelectedItem = null;
                        else
                        {
                            int nextSelect = tabItems.IndexOf(item);
                            if (nextSelect <= 0)
                                nextSelect = 1;
                            else
                                nextSelect--;
                            SelectedItem = tabItems[nextSelect];
                        }
                    }
                    tabItems.Remove(item);
                    item.Dispose();
                    if (tabItems.Count == 0)
                        tabItems.Clear();
                }
            }
            Refresh();
        }

        /// <summary>
        /// close tab by index
        /// </summary>
        /// <param name="index">tab index</param>
        public void CloseTab(int index)
        {
            CloseTab(tabItems[index]);
        }

        public const int horzMargin = 0;
        public const int vertMargin = 2;
        public const int outerMargin = 1;
        public const int buttonWidth = 18;

        private void InitTabButtons()
        {
            lock (tabButtonLocker)
            {
                if (tabButtons == null)
                {
                    tabButtons = new Dictionary<string, XTabButtonItem>();
                    //Init Buttons
                    tabButtons.Add("TABLIST", new XTabButtonItem(this, Lang.Main.Tab_TabListName, Properties.Resources.tablist));
                    tabButtons["TABLIST"].Click += new MouseEventHandler(ShowListClick);
                    tabButtons.Add("NEXT", new XTabButtonItem(this, Lang.Main.Tab_ScrollNext, Properties.Resources.tabnext));
                    tabButtons["NEXT"].Click += new MouseEventHandler(TabNextClick);
                    tabButtons.Add("PREV", new XTabButtonItem(this, Lang.Main.Tab_ScrollPrev, Properties.Resources.tabprev));
                    tabButtons["PREV"].Click += new MouseEventHandler(TabPrevClick);
                }

                foreach (XTabButtonItem tbItem in tabButtons.Values)
                {
                    //set location to out of client rectangle
                    tbItem.Location = new Point(this.Width + 100, this.Height + 100);
                }
            }
        }

        void TabPrevClick(object sender, MouseEventArgs e)
        {
            if (scrollIdx > 0)
                scrollIdx--;
            Refresh();
        }

        void TabNextClick(object sender, MouseEventArgs e)
        {
            scrollIdx++;
            Refresh();
        }

        internal void SelectNextTab()
        {
            if (tabItems == null || tabItems.Count == 0)
                return;
            if (SelectedItem == null)
                SelectedItem = tabItems[0];
            else
            {
                int cidx = tabItems.IndexOf(SelectedItem);
                cidx++;
                if (cidx >= tabItems.Count)
                    return;
                SelectedItem = tabItems[cidx];
            }
        }

        internal void SelectPrevTab()
        {
            if (tabItems == null || tabItems.Count == 0)
                return;
            if (SelectedItem == null)
                SelectedItem = tabItems[tabItems.Count - 1];
            else
            {
                int cidx = tabItems.IndexOf(SelectedItem);
                cidx--;
                if (cidx < 0)
                    return;
                SelectedItem = tabItems[cidx];
            }
        }

        ContextMenuStrip cms = new ContextMenuStrip();
        void ShowListClick(object sender, MouseEventArgs e)
        {
            if(!tabButtons.ContainsKey("TABLIST") || tabItems.Count == 0) return;
            lock (tabButtonLocker)
                tabButtons["TABLIST"].ClickLock = true;
            cms.Items.Clear();
            cms.Closed += new ToolStripDropDownClosedEventHandler(cms_Closed);
            lock (tabItemLocker)
            {
                foreach (var item in tabItems)
                {
                    //item is enum val,so we should cp cur val for evh.
                    var cItem = item;
                    var newItem = new ToolStripMenuItem(cItem.Name);

                    if (item.Selected)
                        newItem.Checked = true;
                    newItem.Click += (s, ev) => SelectedItem = cItem;
                    cms.Items.Add(newItem);
                }
            }
            cms.Show(this, this.Width - buttonWidth, this.Height);
        }

        void cms_Closed(object sender, ToolStripDropDownClosedEventArgs e)
        {
            lock (tabButtonLocker)
            {
                if (!tabButtons.ContainsKey("TABLIST")) return;
                tabButtons["TABLIST"].ClickLock = false;
            }
        }

        static readonly Color topColor = Color.SkyBlue;
        static readonly Color endColor = Color.White;
        static readonly Color lineColor = SystemColors.ControlDarkDark;

        protected override void OnPaint(PaintEventArgs pe)
        {
            using (var lgb = new LinearGradientBrush(ClientRectangle, topColor, endColor, 90f))
            {
                lgb.GammaCorrection = true;
                pe.Graphics.FillRectangle(lgb, ClientRectangle);
                using (var p = new Pen(lineColor))
                    pe.Graphics.DrawLine(p, new Point(0, Height - 1), new Point(Width, Height - 1));
            }

            if (Core.Config == null) return;

            int drawableRight = this.Width - outerMargin;

            if (Core.Config.Appearance.TabAppearance.ShowTabListButton)
                drawableRight -= buttonWidth;

            //freeze array
            lock (tabItemLocker)
            {
                if (tabItems.Count != 0)
                {
                    foreach (var item in tabItems)
                        item.UpdateMeasure(pe.Graphics);

                    //draw
                    if (multiLineMode)
                        OnPaintMultiLine(tabItems.ToArray(), pe, drawableRight);
                    else
                        OnPaintSingleLine(tabItems.ToArray(), pe, drawableRight);
                }
            }

            if (Core.Config.Appearance.TabAppearance.ShowTabListButton)
            {
                lock (tabButtonLocker)
                {
                    if (!tabButtons.ContainsKey("TABLIST"))
                        throw new ApplicationException("FATAL ERROR:TabList button hadn't initialized.");

                    tabButtons["TABLIST"].Location = new Point(drawableRight, outerMargin);
                    tabButtons["TABLIST"].UpdateMeasure(pe.Graphics);
                    tabButtons["TABLIST"].Draw(pe.Graphics);
                }
            }
        }

        private void OnPaintSingleLine(XTabItem[] tabs, PaintEventArgs pe, int drawableRight)
        {
            int leftShift = 0;
            int rightShift = 0;
            drawableRight -= outerMargin;
            //control height check
            if (this.Height != maxTabSize.Height + outerMargin)
            {
                this.Height = maxTabSize.Height + outerMargin;
                //exec refresh immediately.so,we should abort refresh method.
                return;
            }
            //scrollidx validation check
            if(tabs.Length == 0)
                scrollIdx = 0;
            else if (tabs.Length > 0 && tabs.Length <= scrollIdx)
                scrollIdx = tabs.Length - 1;

            if (scrollIdx > 0)
            {
                while (scrollIdx > 0)
                {
                    int remainwidth = drawableRight;
                    for (int i = scrollIdx; i < tabs.Length; i++)
                    {
                        remainwidth -= tabs[i].ClientRectangle.Width;
                        if (remainwidth <= 0) break;
                    }
                    if (remainwidth > tabs[scrollIdx - 1].ClientRectangle.Width)
                        scrollIdx--;
                    else
                        break;
                }
            }

            //pager button check
            bool prevEnabled = false;
            bool nextEnabled = false;

            if (scrollIdx > 0)
            {
                prevEnabled = true;
                leftShift += buttonWidth;
            }

            if (scrollIdx < tabs.Length - 1)
            {
                int remain = drawableRight - leftShift - outerMargin * 2;
                for (int i = scrollIdx; i < tabs.Length && remain >= 0; i++)
                    remain -= tabs[i].ClientRectangle.Width + outerMargin;
                if (remain < 0)
                {
                    nextEnabled = true;
                    rightShift += buttonWidth;
                }
            }

            drawableRight -= rightShift;
            Point drawPos = new Point(outerMargin, outerMargin);
            drawPos.X += leftShift;
            Region defaultClip = pe.Graphics.Clip;

            //gone
            for (int i = 0; i < scrollIdx; i++)
                tabs[i].Location = FARAWAY;

            for (int i = scrollIdx; i < tabs.Length; i++)
            {
                tabs[i].Location = drawPos;
                if (tabs[i].ClientRectangle.Right < drawableRight)
                {
                    pe.Graphics.SetClip(tabs[i].ClientRectangle);
                    tabs[i].Draw(pe.Graphics);
                    drawPos.X += tabs[i].ClientRectangle.Width;
                    drawPos.X++;
                }
                else if (tabs[i].ClientRectangle.Left < drawableRight)
                {
                    //overflow gradation
                    var overflowRect = new Rectangle(tabs[i].ClientRectangle.Location,
                        new Size(drawableRight - tabs[i].ClientRectangle.Left, tabs[i].ClientRectangle.Height));
                    pe.Graphics.SetClip(overflowRect);
                    tabs[i].Draw(pe.Graphics);

                    overflowRect.Y--;
                    overflowRect.Height += 2;

                    using (var path = new GraphicsPath())
                    {
                        path.AddRectangle(overflowRect);
                        using (var pgb = new PathGradientBrush(path))
                        {
                            pgb.CenterColor = Color.Transparent;
                            pgb.CenterPoint = new PointF((float)(overflowRect.Left), (float)overflowRect.Height / 2);
                            pgb.SurroundColors = new[] {
                                Color.Transparent, Color.SkyBlue,
                                Color.White, Color.Transparent 
                            };

                            pe.Graphics.FillRectangle(pgb, overflowRect);
                        }
                    }

                    Rectangle ofLineRect =new Rectangle(overflowRect.X, overflowRect.Bottom - 2, overflowRect.Width, 1);
                    using (var b = new LinearGradientBrush(ofLineRect, Color.Transparent, lineColor, 0f))
                    {
                        b.GammaCorrection = true;
                        pe.Graphics.FillRectangle(b, ofLineRect);
                    }

                    drawPos.X += tabs[i].ClientRectangle.Width;
                    drawPos.X++;
                }
                else
                {
                    tabs[i].Location = FARAWAY;
                }
            }
            pe.Graphics.Clip = defaultClip;

            if (prevEnabled || nextEnabled)
            {
                lock (tabButtons)
                {
                    Point dp = new Point(outerMargin, outerMargin);
                    if (prevEnabled)
                    {
                        //tab button "prev" control
                        if (!tabButtons.ContainsKey("PREV"))
                            throw new ApplicationException("FATAL ERROR:Prev Button Haven't Initialized. - At XTab");
                        tabButtons["PREV"].Location = dp;
                        tabButtons["PREV"].UpdateMeasure(pe.Graphics);
                        tabButtons["PREV"].Draw(pe.Graphics);
                    }
                    else
                    {
                        tabButtons["PREV"].Location = FARAWAY;
                    }

                    dp = new Point(drawableRight, outerMargin);
                    if (nextEnabled)
                    {
                        //tab button "next" control
                        if (!tabButtons.ContainsKey("NEXT"))
                            throw new ApplicationException("FATAL ERROR:Next Button Haven't Initialized. - At XTab");
                        tabButtons["NEXT"].Location = dp;
                        tabButtons["NEXT"].UpdateMeasure(pe.Graphics);
                        tabButtons["NEXT"].Draw(pe.Graphics);
                    }
                    else
                    {
                        tabButtons["NEXT"].Location = FARAWAY;
                    }
                }
            }
        }

        private void OnPaintMultiLine(XTabItem[] tabs, PaintEventArgs pe, int drawableRight)
        {
            Point drawPos = new Point(outerMargin, outerMargin);
            int lines = 1;
            for (int i = 0; i < tabs.Length; i++)
            {
                tabs[i].Location = drawPos;
                if (tabs[i].ClientRectangle.Right > drawableRight)
                {
                    //newline
                    drawPos.Y += outerMargin + MaxTabSize.Height;
                    drawPos.X = outerMargin;
                    lines++;
                    tabs[i].Location = drawPos;
                }
                pe.Graphics.SetClip(tabs[i].ClientRectangle);
                tabs[i].Draw(pe.Graphics);
                drawPos.X += tabs[i].ClientRectangle.Width;
            }
            if (this.Height != lines * (MaxTabSize.Height + outerMargin) + outerMargin)
            {
                this.Height = lines * (MaxTabSize.Height + outerMargin);
            }
        }
    }
}
